Generated code - Using the EntityView2 class, Adapter
Preface
The EntityView2 is a class which is used to create in-memory views on an EntityCollection object (generic or non-generic) and allows you to filter and
sort an in-memory EntityCollection
without actually touching the data inside the EntityCollection. An EntityCollection can have multiple EntityView2 objects, similar to
the DataTable - DataView combination.
This section describes how to use the EntityView2 class in various different scenarios.
DataBinding and EntityView2 classes
The EntityCollection class doesn't bind directly to a bound control, it always bind through its EntityView2 object
(returned by the property DefaultView, see below).
The EntityView2 approach allows you to create multiple EntityView2 instances on a single EntityCollection and all bind them to different controls as
if they're different sets of data.
Creating an EntityView2 instance
Creating an EntityView2 object is simple
// C#
EntityCollection<CustomerEntity> customers = new EntityCollection<CustomerEntity>(new CustomerEntityFactory());
adapter.FetchEntityCollection(customers, null); // fetch all customers
EntityView2<CustomerEntity> customerView = new EntityView2<CustomerEntity>(customers);
' VB.NET
Dim customers As New EntityCollection(Of CustomerEntity)(new CustomerEntityFactory())
adapter.FetchEntityCollection(customers, Nothing) ' fetch all customers
Dim customerView As New EntityView2(Of CustomerEntity)(customers)
This creates an EntityView2 object
on the EntityCollection customers, so it lets you
view the data in the EntityCollection 'customers'. EntityView2
objects don't contain any data: all data you'll be able to access through an EntityView2 is actually data residing in the related EntityCollection.
You can also use the EntityCollection's DefaultView property to create an EntityView2. This is similar to the DataTable's DefaultView property: every time you
read the property, you'll get the same view object back. This is also true for the EntityCollection's DefaultView property:
// C#
EntityCollection<CustomerEntity> customers = new EntityCollection<CustomerEntity>(new CustomerEntityFactory());
adapter.FetchEntityCollection(customers, null); // fetch all customers
EntityView2<CustomerEntity> customerView = customers.DefaultView;
// or:
// IEntityView2 customerView = customers.DefaultView;
' VB.NET
Dim customers As New EntityCollection(Of CustomerEntity)(new CustomerEntityFactory())
adapter.FetchEntityCollection(customers, Nothing) ' fetch all customers
Dim customerView As EntityView2(Of CustomerEntity) = customers.DefaultView
' or:
' Dim customerView As IEntityView2 = customers.DefaultView
Instead of using the EntityView class, you can use the IEntityView interface, if you for example don't know the generic type code.
The EntityView2 constructor has various overloads which let you specify an initial filter and / or sort expression. You can also set the filter and / or sort
expression later on as described below. Please familiar yourself with the various methods and properties of the EntityView2 class, by checking its entry in
the LLBLGen Pro reference manual.
Filtering and sorting an EntityView2
The purpose of an EntityView2 is to give you a 'view' based on a filter and / or a sortexpression on an in-memory EntityCollection. Which data contained in
the related EntityCollection is available to you through a particular EntityView2 object depends on the filter set for the EntityView2. In which order the
data is available to you is controlled by the set sort expression. As the related collection is not touched, you can have as many EntityView2 objects
on the same EntityCollection, all exposing different subsets of the data in the EntityCollection, in different order.
Filtering and sorting an EntityView2 is done through normal LLBLGen Pro predicate and sortclause classes. See for more information about predicate classes:
Getting started with filtering and
The predicate system.
The following example filters the aforementioned customers collection on all customers from the UK:
// C#
IPredicate filter = (CustomerFields.Country == "UK");
customerView.Filter = filter;
' VB.NET
Dim filter As IPredicate = (CustomerFields.Country = "UK")
customerView.Filter = filter
You also could have specified this filter with the EntityView2 constructor. As soon as the EntityView2's property
Filter is set to a value, the EntityView2
object resets itself and will apply the set IPredicate to the related EntityCollection and all matching entity objects will be available through the EntityView2
object.
This is similar to the EntityView2's sorter. Let's sort our filtered EntityView2 on CompanyName, ascending. For more information about sortclauses and
sortexpression objects, please see:
Generated code - Sorting.
// C#
ISortExpression sorter = new SortExpression(CustomerFields.CompanyName | SortOperator.Ascending);
customerView.Sorter = sorter;
' VB.NET
Dim sorter As New SortExpression(CustomerFields.CompanyName Or SortOperator.Ascending)
customerView.Sorter = sorter
Use a Predicate(Of T) or Lambda expression (.NET 3.5+) for a filter
In .NET 2.0, Microsoft introduced a new class called
Predicate<T>. This is a class which is used in a couple of methods in List<T> and Array for example. In .NET 3.5, Lambda expressions were introduced, which are actually
Func<T, U> (and variants) implementations. The .NET 3.5 compilers will compile a lambda expression to a Predicate<T> if the method requires a Predicate<T>, as both are under the surface simply delegates. EntityView2 has a couple of constructors which accept a Predicate<T>. This allows you to specify a lambda expression in .NET 3.5+ to filter the entity collection, or if you're on .NET 2.0/3.0, you can use a delegate which compiles to Predicate<T>. The example below filters the passed in collection of CustomerEntity instances on the Country property:
EntityView2<CustomerEntity> customersFromGermany =
new EntityView2<CustomerEntity>(customers, c=>c.Country=="Germany");
Dim customersFromGermany = _
New EntityView2(Of CustomerEntity)(customers, Function(c) c.Country="Germany")
Using the DelegatePredicate<T>, a developer can also use a Predicate<T> delegate or Lambda expression to filter the EntityView2 instance after it's been created:
EntityView2<CustomerEntity> customersFromGermany =
new EntityView2<CustomerEntity>(customers);
customersFromGermany.Filter = new DelegatePredicate<CustomerEntity>(c=>c.Country=="Germany");
Dim customersFromGermany = _
New EntityView2(Of CustomerEntity)(customers)
customersFromGermany.Filter = New DelegatePredicate(Of CustomerEntity)(Function(c) c.Country="Germany")
Multi-clause sorting
The EntityCollection class offers a Sort() method, which is there for backwards compatibility and was used in previous versions by the
IBindingList.ApplySort() method. The Sort() method however has one drawback: it can only sort on a single field or property. What if you want to
sort on multiple fields? As the EntityView2 allows you to sort the data using a SortExpression, you can specify as much fields as you want. Let's sort the
customerView on City ascending and on CompanyName descending:
// C#
ISortExpression sorter = new SortExpression(CustomerFields.City | SortOperator.Ascending);
sorter.Add(CustomerFields.CompanyName | SortOperator.Descending);
customerView.Sorter = sorter;
' VB.NET
Dim sorter As New SortExpression(CustomerFields.City Or SortOperator.Ascending)
sorter.Add(CustomerFields.CompanyName Or SortOperator.Descending)
customerView.Sorter = sorter
What if you want to sort on a property of an entity, which isn't an entity field? After all, Sort() allows you to do that. This is also possible: to specify
a property, you've to use the class
EntityProperty instead of an entity field. So if you instead of sorting on CompanyName, want to sort on the
entity property
IsDirty, to get all the changed entities first, and then the non-changed entities, you've to use this code instead:
// C#
ISortExpression sorter = new SortExpression(CustomerFields.City | SortOperator.Ascending);
sorter.Add(new EntityProperty("IsDirty") | SortOperator.Ascending);
customerView.Sorter = sorter;
' VB.NET
Dim sorter As New SortExpression(CustomerFields.City Or SortOperator.Ascending)
sorter.Add(New EntityProperty("IsDirty") Or SortOperator.Descending)
customerView.Sorter = sorter
EntityProperty is usable in any construct which works with an entityfield, as long as it's in-memory sorting or filtering. Below you'll learn how to filter
an EntityView2's data using an entity property.
Filtering using multiple predicates
As a PredicateExpression derives from Predicate, you can also use a PredicateExpression to filter using multiple predicates. There's a limitation however: not all
predicate classes are usable for in-memory filtering: please consult the section
Generated code - The predicate system which classes are usable and with which specifics. The filtering is also
focussed on the entities inside the related EntityCollection, not on entities inside those entities. This thus means you can't specify a RelationCollection
for example to filter all Customers who have an Order from last May.
To filter the customers collection on all customers from the UK which entities have been changed, use the following code:
// C#
IPredicateExpression filter = new PredicateExpression(CustomerFields.Country == "UK");
filter.AddWithAnd(new EntityProperty("IsDirty") == true);
customerView.Filter = filter;
' VB.NET
Dim filter As New PredicateExpression(CustomerFields.Country = "UK")
filter.AddWithAnd(New EntityProperty("IsDirty") = True)
customerView.Filter = filter
View behavior on collection changes
When an entity changes in the related EntityCollection of the EntityView2, it can be the entity doesn't match anymore with the filter set for the view and the
EntityView2 therefore removes the entity from itself: it's no longer available to you through the EntityView2.
This can be confusing so it is definable what the EntityView2 should do when the data inside the related EntityCollection changes. This is done by specifying a
PostCollectionChangeAction value with the EntityView2 constructor or by setting the EntityView2's
DataChangeAction property.
The following list describes the various values and their result on the EntityView2's behavior:
- NoAction (do nothing), i.e.: don't re-apply the filter nor the sorter.
- ReapplyFilterAndSorter (default). Reapplies the filter and sorter on the collection.
- ReapplySorter. Reapplies the sorter on the collection, not the filter.
By default, the EntityView2 will re-apply the filter and sorter. There's no setting for just the filter, as re-applying the filter could alter the set, which could
change the order of the data as in: it's no longer ordered and has to be re-sorted. If the related collection fires a reset event (when it is sorted using
its own code or cleared), the view is also reset and filters are re-applied as well as sorters.
If a new entity is added to the collection through code, it is not added to the view in NoAction mode or in ReapplySorter mode, because no filter is re-applyed.
If it's added through databinding, it actually
is added to the view, as it is added through the EntityView2, because an EntityCollection is bound to
a bound control via an EntityView2, either an EntityView2 object you created and bound directly or through the EntityView2 object returned by the EntityCollection's
DefaultView property.
Projecting data inside an EntityView2 on another data-structure
A powerful feature of the EntityView2 class is the ability to
project the data in the EntityView2 onto a new data-structure, like an EntityCollection,
datatable or even custom classes. Projections are a way to produce custom lists of data ('dynamic lists in memory') based on the current data
in the EntityView2 and a collection of
projection objects. Projection objects are small objects which specify which entity field or entity property
should be used in the projection and where to get the value from. For example, because the raw projection data can be used to
re-instantiate new entities, the data can be used to produce a new EntityCollection with new entities. How the data is projected depends on the projection
engine used for the actual projection. For more information about projections please also see:
LLBLGen Pro - Fetching DataReaders and projections.
Projections are performed by applying a set of projection objects onto an entity and then by passing on the result data array for further storage to a
projection engine, or projector, the projected data is placed in a new instance of a class, for example an entity class, but this can also be a DataRow
or a custom class. (Projections on custom classes are only supported on .NET
2.0). The array is an array of type object. You can use filters during the
projection as well, to limit the set of data you want to project from the
EntityView2 data.
Projection objects: EntityPropertyProjector
A projection object is an instance of the
EntityPropertyProjector class. As EntityView2 objects contain Entity objects, this is the projection object
you should use. LLBLGen Pro supports other projection objects as well, for general purpose projections as discussed in
Fetching DataReaders and projections, however these aren't usable with EntityView2s.
An EntityPropertyProjector instance contains at most two IEntityFieldCore instances (for example normal EntityField2 objects or an EntityProperty object)
and a Predicate, for example a FieldCompareValuePredicate, or a PredicateExpression. The first IEntityFieldCore instance is mandatory. This is the default value.
If a Predicate is specified (optional), and it resolves to true, the default value (thus the first IEntityFieldCore) is used, otherwise
the second IEntityFieldCore instance.
This way you can select per entity from two fields, for example SomeEntity.Name1 and SomeEntity.Name2, based on the predicate specified either
the value of field Name1 of the entity, if the predicate resolves to true, otherwise the value of Name2.
The EntityPropertyProjector also contains a Name property which is used to produce the name of the result field. The projection
routine used is free to use this name for column purpose (projection onto a datatable) but can also use it for entity field setting (projection onto an entity).
If a developer wants to execute a piece of code onto the value prior to storing it into the projected slot, the developer can derive his own class from
EntityPropertyProjector and override ValuePostProcess(). This routine is normally empty and expects the value and the entity being processed.
It all might sound a little complex, but it's fairly straigt forward, as will be shown in a couple of examples below.
Projecting an EntityView2's data is done by the
CreateProjection routine of an EntityView2 object. LLBLGen Pro comes with three different projection
engines: one for projecting data onto a DataTable (the class DataProjectorToDataTable), one for projecting data onto an EntityCollection
(the class DataProjectorToEntityCollection) and on .NET 2.0 also one for projecting data onto a list of custom classes (the class
DataProjectorToCustomClass). You can write your own projection engine as well: simply implement the interface IEntityDataProjector to be able to use
the engine in projections of EntityView2 data. If you also want to use the same engine in projections of resultsets as discussed in
Fetching DataReaders and projections, you also should implement the almost similar interface
IGeneralDataProjector. Because the interfaces can re-use the actual projection engine logic, it's easy to re-use projection code for both projection mechanisms.
Only the data which is available to you through the EntityView2 can possibly be projected. You can't project nested data inside entities nor entity data not in the
EntityView2. In that case, create a new EntityView2 on the same EntityCollection using a different filter and project that EntityView2 object instead.
Creating EntityPropertyProjector instances for all entity fields.
Sometimes you want to project all fields of a given entity and it
can be cumbersome to create a lot of EntityPropertyProjector objects if your entity has a lot of fields. Instead, you can use the shortcut method on
EntityFields2: EntityFields2.ConvertToProjectors( EntityFieldsFactory.CreateEntityFieldsObject(EntityType.
entitynameEntity))
This method will return List of IEntityPropertyProjector objects, one for each entity field of the specified entity type.
Examples of EntityView2 projections
Projection to datatable.
// C#
EntityCollection<CustomerEntity> customers = new EntityCollection<CustomerEntity>(new CustomerEntityFactory());
adapter.FetchEntityCollection(customers, null); // fetch all customers
// create a view of all customers in germany
EntityView2<CustomerEntity> customersInGermanyView = new EntityView2<CustomerEntity>( customers,
(CustomerFields.Country == "Germany"), null );
// create projection of these customers of just the city and the customerid.
// for that, define 2 propertyprojectors, one for each field to project
List<IEntityPropertyProjector> propertyProjectors= new List<IEntityPropertyProjector>();
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.City, "City" ) );
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID" ) );
DataTable projectionResults = new DataTable();
// create the actual projection.
customersInGermanyView.CreateProjection( propertyProjectors, projectionResults );
' VB.NET
Dim customers As New EntityCollection(Of CustomerEntity)(New CustomerEntityFactory())
adapter.FetchEntityCollection(customers, nothing) ' fetch all customers
' create a view of all customers in germany
Dim customersInGermanyView As New EntityView2(Of CustomerEntity)( customers, _
New FieldCompareValuePredicate(CustomerFields.Country, Nothing, ComparisonOperator.Equal, "Germany"), Nothing)
' create projection of these customers of just the city and the customerid.
' for that, define 2 propertyprojectors, one for each field to project
Dim propertyProjectors As New List(Of IEntityPropertyProjector)()
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.City, "City" ) )
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID" ) )
Dim projectionResults As New DataTable()
' create the actual projection.
customersInGermanyView.CreateProjection( propertyProjectors, projectionResults )
After this code, the datatable
projectionResults contains two columns, City and CustomerID, and it contains the data for the fields City and CustomerId of
each entity in the EntityView2, which are all entities with Country equal to "Germany".
Projection to EntityCollection
The following example performs a projection onto an EntityCollection. 'Clerk' is another subtype of
'Employee'.
// C#
// fetch all managers
EntityCollection<ManagerEntity> managers = new EntityCollection<ManagerEntity>(new ManagerEntityFactory());
adapter.FetchEntityCollection(managers, null);
// now project them onto 2 new clerk entities, by just projecting the employee fields
List<IEntityPropertyProjector> propertyProjectors = new List<IEntityPropertyProjector>();
propertyProjectors.Add( new EntityPropertyProjector( EmployeeFields.Id, "Id" ) );
propertyProjectors.Add( new EntityPropertyProjector( EmployeeFields.Name, "Name" ) );
propertyProjectors.Add( new EntityPropertyProjector( EmployeeFields.StartDate, "StartDate" ) );
propertyProjectors.Add( new EntityPropertyProjector( EmployeeFields.WorksForDepartmentId, "WorksForDepartmentId" ) );
EntityCollection<ClerkEntity> clerks = new EntityCollection<ClerkEntity>(new ClerkEntityFactory());
EntityView2<ManagerEntity> managersView = managers.DefaultView;
// project data to transform all managers into clerks. ;)
managersView.CreateProjection( propertyProjectors, clerks );
' VB.NET
' fetch all managers
Dim managers As New EntityCollection(Of ManagerEntity)(new ManagerEntityFactory())
adapter.FetchEntityCollection(managers, Nothing)
' now project them onto 2 new clerk entities, by just projecting the employee fields
Dim propertyProjectors As New List(Of IEntityPropertyProjector)()
propertyProjectors.Add( New EntityPropertyProjector( EmployeeFields.Id, "Id" ) )
propertyProjectors.Add( New EntityPropertyProjector( EmployeeFields.Name, "Name" ) )
propertyProjectors.Add( New EntityPropertyProjector( EmployeeFields.StartDate, "StartDate" ) )
propertyProjectors.Add( New EntityPropertyProjector( EmployeeFields.WorksForDepartmentId, "WorksForDepartmentId" ) )
Dim clerks As New EntityCollection(Of ClerkEntity)(new ClerkEntityFactory())
Dim managersView As EntityView2(Of ManagerEntity) = managers.DefaultView
' project data to transform all managers into clerks. ;)
managersView.CreateProjection( propertyProjectors, clerks )
After this code, the collection
clerks contains ClerkEntity instances with only the EmployeeEntity fields (inherited by ClerkEntity from its base type
EmployeeEntity, which is also the base type of ManagerEntity) filled with data.
projection to custom classes
With some reflection, it is possible to
create such a class for .NET 1.x, though the class itself has to be setup a little different. The code below also shows how to use the projectors in .NET 2.0.
It uses the class TestCustomer which is given below the projection example code (in C#). The projection also shows how to project a property of an entity which
isn't an entity field, namely IsDirty, using the EntityProperty class.
// C#
EntityCollection<CustomerEntity> customers =
new EntityCollection<CustomerEntity>(new CustomerEntityFactory());
adapter.FetchEntityCollection(customers, null);
EntityView2<CustomerEntity> allCustomersView = customers.DefaultView;
List<TestCustomer> customCustomers = new List<TestCustomer>();
DataProjectorToCustomClass<TestCustomer> customClassProjector =
new DataProjectorToCustomClass<TestCustomer>( customCustomers );
List<IEntityPropertyProjector> propertyProjectors = new List<IEntityPropertyProjector>();
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID" ) );
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.City, "City" ) );
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.CompanyName, "CompanyName" ) );
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.Country, "Country" ) );
propertyProjectors.Add( new EntityPropertyProjector( new EntityProperty("IsDirty"), "IsDirty" ) );
// create the projection
allCustomersView.CreateProjection( propertyProjectors, customClassProjector );
' VB.NET
Dim customers As New EntityCollection(Of CustomerEntity)(New CustomerEntityFactory())
adapter.FetchEntityCollection(customers, Nothing)
Dim allCustomersView As EntityView2(Of CustomerEntity) = customers.DefaultView
Dim customCustomers As New List(Of TestCustomer)()
Dim customClassProjector As New DataProjectorToCustomClass(Of TestCustomer)( customCustomers )
Dim propertyProjectors As New List(Of IEntityPropertyProjector)()
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID" ) )
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.City, "City" ) )
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.CompanyName, "CompanyName" ) )
propertyProjectors.Add( New EntityPropertyProjector( CustomerFields.Country, "Country" ) )
propertyProjectors.Add( New EntityPropertyProjector( new EntityProperty("IsDirty"), "IsDirty" ) )
' create the projection
allCustomersView.CreateProjection( propertyProjectors, customClassProjector )
The custom class, TestCustomer:
///
/// Test class for projection of fetched entities onto custom classes using a custom projector.
///
public class TestCustomer
{
#region Class Member Declarations
private string _customerID, _companyName, _city, _country;
private bool _isDirty;
#endregion
public TestCustomer()
{
_city = string.Empty;
_companyName = string.Empty;
_customerID = string.Empty;
_country = string.Empty;
_isDirty = false;
}
#region Class Property Declarations
public string CustomerID
{
get { return _customerID; }
set { _customerID = value; }
}
public string City
{
get { return _city; }
set { _city = value; }
}
public string CompanyName
{
get { return _companyName; }
set { _companyName = value; }
}
public string Country
{
get { return _country; }
set { _country = value; }
}
public bool IsDirty
{
get { return _isDirty; }
set { _isDirty = value; }
}
#endregion
}
Distinct projections.
It can be helpful to have distinct projections: no duplicate data in the projection results. Distinct projections are supported, as the following example will show.
Creating a distinct projection is simply passing false / False for allowDuplicates in the CreateProjection method.
The following example shows a couple of projection related aspects: it filters the entity view's data using a Like predicate prior to projecting data, so you can
limit the data inside an EntityView2 used for the projection, and it shows an example how
a predicate is used to choose between two values in an entity to determine the end result of projecting an entity. The example uses Northwind like most
examples in this documentation. The code contains Assert statements, which are left to show you how many elements to expect at that point in the routine.
EntityCollection<CustomerEntity> customers =
new EntityCollection<CustomerEntity>( new CustomerEntityFactory() );
adapter.FetchEntityCollection( customers, null );
EntityView2<CustomerEntity> customersInGermanyView =
new EntityView2<CustomerEntity>( customers, (CustomerFields.Country == "Germany"), null );
Assert.AreEqual( 11, customersInGermanyView.Count );
// create straight forward projection of these customers of just the city and the customerid.
List<IEntityPropertyProjector> propertyProjectors= new List<IEntityPropertyProjector>();
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.City, "City" ) );
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID" ) );
DataTable projection = new DataTable();
customersInGermanyView.CreateProjection( propertyProjectors, projection );
Assert.AreEqual( 11, projection.Rows.Count );
// do distinct filtering during the following projection. It projects ContactTitle and IsNew
propertyProjectors = new List<IEntityPropertyProjector>();
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.ContactTitle, "Contact title" ) );
// any entity property can be used for projection source.
propertyProjectors.Add( new EntityPropertyProjector( new EntityProperty( "IsNew" ), "Is new" ) );
projection = new DataTable();
customersInGermanyView.CreateProjection( propertyProjectors, projection, false );
Assert.AreEqual( 7, projection.Rows.Count );
// do distinct filtering and filter the set to project. Re-use previous property projectors.
// 3 rows match the specified filter, distinct filtering makes it 2.
projection = new DataTable();
customersInGermanyView.CreateProjection( propertyProjectors, projection, false, (CustomerFields.ContactTitle % "Marketing%") );
Assert.AreEqual( 2, projection.Rows.Count );
// use alternative projection source based on filter.
projection = new DataTable();
propertyProjectors = new List<IEntityPropertyProjector>();
// bogus data, but performs what we need: for all contacttitles not matching the filter, CustomerId is used.
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.ContactTitle,
"Contact title", (CustomerFields.ContactTitle % "Marketing%"), CustomerFields.CustomerId) );
propertyProjectors.Add( new EntityPropertyProjector( CustomerFields.CustomerId, "CustomerID" ) );
// create a new projection, with distinct filtering, which gives different results now, because ContactTitle is now sometimes equal to CustomerId
customersInGermanyView.CreateProjection( propertyProjectors, projection, false );
Assert.AreEqual( 11, projection.Rows.Count );
foreach( DataRow row in projection.Rows )
{
if( !row["Contact title"].ToString().StartsWith( "Marketing" ) )
{
Assert.AreEqual( row["Contact title"], row["CustomerID"] );
}
}
Aggregates aren't supported in in-memory projections though Expressions are. All expressions are fully evaluated,
where '+' operators on strings result in string concatenations. The new DbFunctionCall object to call database functions inside an
Expression object is ignored during expression evaluation.